package com.culture.config.common.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.CollectionUtils;

import com.github.pagehelper.Page;

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@UtilityClass
public class BeanUtil {

	/**
	 * instantiateClass
	 * @param <T>
	 * @param classz
	 * @return
	 */
	public static <T> T instantiateClass(Class<T> classz){
		return BeanUtils.instantiateClass(classz);
	}
	
	/**
	 * copyProperties
	 * @see org.springframework.beans.BeanUtils.copyProperties
	 * @param <T>
	 * @param source
	 * @param classz
	 * @return
	 */
	public static <T> T copyProperties(Object source, Class<T> classz) {
		if(Objects.isNull(source) || Objects.isNull(classz)) {
			return null;
		}
		T obj = BeanUtils.instantiateClass(classz);			
		BeanUtils.copyProperties(source, obj);		
		return obj;
	}	
	
	/**
	 * copyListProperties
	 * @see org.springframework.beans.BeanUtils.copyProperties
	 * @param <T>
	 * @param sourceList
	 * @param classz
	 * @return
	 */
	public static <T> List<T> copyListProperties(List<?> sourceList, Class<T> classz){
		if(CollectionUtils.isEmpty(sourceList) || Objects.isNull(classz)) {
			return Collections.emptyList();
		}
		
		List<T> targetList = new LinkedList<>();
		sourceList.forEach(e->{			
			T obj = BeanUtils.instantiateClass(classz);	
			BeanUtils.copyProperties(e, obj);
			targetList.add(obj);			
		});		
		return targetList;
	}
	
	/**
	 * copyPageProperties
	 * @param <T>
	 * @param sourceList
	 * @param classz
	 * @return
	 */	
	public static <T> Page<T> copyPageProperties(Page<?> sourceList, Class<T> classz){
		return copyPageProperties(sourceList,classz,null);
	}

	/**
	 * copy PageProperties
	 * @param <E>
	 * @param <T>
	 * @param sourceList
	 * @param classz
	 * @param parserConsumer
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <E,T> Page<T> copyPageProperties(Page<E> sourceList, Class<T> classz, BiConsumer<E,T> parserConsumer){
		if(Objects.isNull(sourceList) || Objects.isNull(classz)) {
			return new Page<>();
		}
		Page<T> targetPage = BeanUtil.copyProperties(sourceList, Page.class);
		if(Objects.isNull(targetPage)){
			return new Page<>();
		}		
		targetPage.clear();
		sourceList.getResult().forEach(e->{
			T obj = BeanUtils.instantiateClass(classz);	
			BeanUtils.copyProperties(e, obj);
			if(Objects.nonNull(parserConsumer)){
				parserConsumer.accept(e, obj);
			}			
			targetPage.add(obj);			
		});		
		return targetPage;
	}	
	
	/**
	 * setProperty
	 * @see org.springframework.beans.BeanWrapper
	 * @param obj
	 * @param fieldName
	 * @param value
	 */
	public static void setProperty(Object bean, String fieldName, Object value) {
		if(Objects.isNull(bean) || StringUtils.isBlank(fieldName)) {
			return;
		}
		if(!hasProperty(bean,fieldName)) {
			return;
		}
		BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
		beanWrapper.setPropertyValue(fieldName, value);
	}
	
	/**
	 * getProperty
	 * @see org.apache.commons.beanutils.BeanUtils.getProperty
	 * @param bean
	 * @param fieldName
	 * @return Property value or null if not exist or Exception
	 * @throws NoSuchMethodException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 */
	public static Object getProperty(Object bean, String fieldName) throws IllegalAccessException, InvocationTargetException,
		NoSuchMethodException {
		if(Objects.isNull(bean) || StringUtils.isBlank(fieldName)) {
			return null;
		}	
		return org.apache.commons.beanutils.BeanUtils.getProperty(bean, fieldName);			
	}
		
	/**
	 * populate
	 * @see org.apache.commons.beanutils.BeanUtils.populate
	 * @param bean
	 * @param properties
	 * @return success:true ,failed: false
	 */
	public static boolean populate(Object bean, Map<String,? extends Object> properties) {
		if(MapUtils.isEmpty(properties) || Objects.isNull(bean)) {
			return false;
		}	
		boolean ret = false;
		 try {
			org.apache.commons.beanutils.BeanUtils.populate(bean, properties);
			ret = true;
		} catch ( Exception ex) {
			log.error("copyProperties exception:{}",ex);
		}
		return ret;
	}
	
	/**
	 * hasProperty, only check the root properties
	 * @param bean
	 * @param fieldName
	 * @return boolean true or false
	 */
	public static boolean hasProperty(Object bean, String fieldName) {
		boolean ret = false;
		if(Objects.isNull(bean)) {
			return ret;
		}
		Field[] fields = bean.getClass().getDeclaredFields();
		for(Field f: fields) {
			if(f.getName().endsWith(fieldName)){
				ret = true;
				break;
			}
		}
		return ret; 
	}
}
